﻿///////////////////////////////////////////////////////////////////////////////
// File:        RealmdSocket.cc
// Author:      671643387@qq.com
// Date:        2016年1月18日 上午11:18:29
// Description:
///////////////////////////////////////////////////////////////////////////////

#include "Log.h"
#include "RealmdSocket.h"
#include "Realmd.h"
#include "Database.h"
#include "RealmList.h"

enum eStatus
{
    STATUS_CONNECTED = 0,
    STATUS_AUTHED
};

typedef struct AuthHandler
{
    eAuthCmd cmd;
    ::std::uint32_t status;
    bool (RealmdSocket::*handler)(void);
} AuthHandler;

const AuthHandler table[] =
{
    { CMD_AUTH_LOGON_CHALLENGE,     STATUS_CONNECTED, &RealmdSocket::_HandleLogonChallenge    },
    { CMD_AUTH_LOGON_PROOF,         STATUS_CONNECTED, &RealmdSocket::_HandleLogonProof        },
    { CMD_AUTH_RECONNECT_CHALLENGE, STATUS_CONNECTED, &RealmdSocket::_HandleReconnectChallenge},
    { CMD_AUTH_RECONNECT_PROOF,     STATUS_CONNECTED, &RealmdSocket::_HandleReconnectProof    },
    { CMD_REALM_LIST,               STATUS_AUTHED,    &RealmdSocket::_HandleRealmList         }
};

#define AUTH_TOTAL_COMMANDS sizeof(table)/sizeof(AuthHandler)

extern __MWOW_DB_ gLoginDB;
extern RealmList gRealmList;

#define s_BYTE_SIZE 32

RealmdSocket::RealmdSocket(void)
    : authed_(false)
    , accountSecLv_(SEC_PLAYER)
    , build_(0)
{
    N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
    g.SetUInt32(7);
}

RealmdSocket::~RealmdSocket(void)
{

}

void RealmdSocket::OnOpen(void)
{
    MWOW_DEBUG << "[" << GetId() 
        << "] player connect from " 
        << GetPeerAddr() 
        << ":" << GetPeerPort();
}

void RealmdSocket::OnClose(void)
{
    MWOW_DEBUG << "[" << GetId() 
        << "] player disconnect.";
}

void RealmdSocket::OnRecv(void)
{
    ::std::uint8_t cmd;
    while (true)
    {
        if (RecvPeek(&cmd, 1) != 1)
            return;

        ::std::size_t x;
        for (x = 0; x < AUTH_TOTAL_COMMANDS; ++x)
        {
            if ( ( (::std::uint8_t)table[x].cmd == cmd ) && 
                ( ( table[x].status == STATUS_CONNECTED ) || 
                ( authed_ && table[x].status == STATUS_AUTHED ) ) )
            {
                MWOW_DEBUG_F("[Auth] got data for cmd %u recv length %u",
                    (::std::uint32_t)cmd, (::std::uint32_t)RecvSize());

                if (!(*this.*table[x].handler)())
                {
                    MWOW_DEBUG_F("Command handler failed for cmd %u recv length %u",
                        (::std::uint32_t)cmd, (::std::uint32_t)RecvSize());
                    return;
                }

                break;
            }
        }

        if (x == AUTH_TOTAL_COMMANDS)
        {
            // 未知命令，关闭连接
            MWOW_DEBUG_F("[Auth] got unknown packet %u", (::std::uint32_t)cmd);
            Close();
            return;
        }
    }
}

bool RealmdSocket::_HandleLogonChallenge(void)
{
    MWOW_DEBUG_F("Entering _HandleLogonChallenge");
    if (RecvSize() < sizeof(sAuthLogonChallenge_C))
    { return false; }

    // 读包头
    std::vector<::std::uint8_t> buf;
    buf.resize(4);

    Recv((::std::uint8_t*)&buf[0], 4);

    ::std::uint16_t remaining = ((sAuthLogonChallenge_C*)&buf[0])->size;
    MWOW_DEBUG_F("[AuthChallenge] got header, body is %#04x bytes", remaining);

    if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (RecvSize() < remaining))
    { return false; }

    buf.resize(remaining + buf.size() + 1);
    buf[buf.size() - 1] = 0;
    sAuthLogonChallenge_C* ch = (sAuthLogonChallenge_C*)&buf[0];

    // 读取剩下的数据
    Recv((::std::uint8_t*)&buf[4], remaining);
    MWOW_DEBUG_F("[AuthChallenge] got full packet, %#04x bytes", ch->size);
    MWOW_DEBUG_F("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I);

    __YXS_BYTEBUFFER_ pkt;

    loginName_ = (const char*)ch->I;
    build_ = ch->build;
    safeLoginName_ = loginName_;
    gLoginDB.EscapeString(safeLoginName_);

    pkt << (::std::uint8_t) CMD_AUTH_LOGON_CHALLENGE;
    pkt << (::std::uint8_t) 0x00;

    // 检查IP地址是否被禁止登录
    ::std::string address = GetPeerAddr();
    gLoginDB.EscapeString(address);
    __YXS_SQLRESULTSET_PTR_ result = gLoginDB.Query("SELECT unbandate FROM ip_banned WHERE "
        //    permanent                    still banned
        "(unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'", address.c_str());
    if (result)
    {
        pkt << (::std::uint8_t)WOW_FAIL_BANNED;
        MWOW_INFO_F("[AuthChallenge] Banned ip %s tries to login!", GetPeerAddr().c_str());
    }
    else
    {
        result = gLoginDB.Query("SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '%s'", safeLoginName_.c_str());
        if (result)
        {
            // 检查帐号是否被锁定登录IP，如果被锁定检查当前登录IP是否是锁定值
            bool locked = false;
            if ((*result)[2]->GetUInt8() == 1)
            {
                MWOW_DEBUG_F("[AuthChallenge] Account '%s' is locked to IP - '%s'", loginName_.c_str(), (*result)[3]->GetCString());
                MWOW_DEBUG_F("[AuthChallenge] Player address is '%s'", GetPeerAddr().c_str());
                if (strcmp((*result)[3]->GetCString(), GetPeerAddr().c_str()))
                {
                    MWOW_DEBUG_F("[AuthChallenge] Account IP differs");
                    pkt << (::std::uint8_t) WOW_FAIL_SUSPENDED;
                    locked = true;
                }
                else
                {
                    MWOW_DEBUG_F("[AuthChallenge] Account IP matches");
                }
            }
            else
            {
                MWOW_DEBUG_F("[AuthChallenge] Account '%s' is not locked to ip", loginName_.c_str());
            }

            // 帐号没有被锁定登录IP或当前登录IP等于锁定值
            if (!locked)
            {
                // 检查帐号是否被禁止登录
                __YXS_SQLRESULTSET_PTR_ banresult = gLoginDB.Query("SELECT bandate,unbandate FROM account_banned WHERE "
                    "id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)", (*result)[1]->GetUInt32());
                if (banresult)
                {
                    if ((*banresult)[0]->GetUInt64() == (*banresult)[1]->GetUInt64())
                    {
                        pkt << (::std::uint8_t) WOW_FAIL_BANNED;
                        MWOW_INFO_F("[AuthChallenge] Banned account %s tries to login!", loginName_.c_str());
                    }
                    else
                    {
                        pkt << (::std::uint8_t) WOW_FAIL_SUSPENDED;
                        MWOW_INFO_F("[AuthChallenge] Temporarily banned account %s tries to login!", loginName_.c_str());
                    }
                }
                else
                {
                    // 帐号没有被禁止登录
                    ::std::string rI = (*result)[0]->GetString();

                    // 从数据库读取v,s的值
                    std::string dbV = (*result)[5]->GetString();
                    std::string dbS = (*result)[6]->GetString();
                    MWOW_DEBUG_F("database authentication values: v='%s' s='%s'", dbV.c_str(), dbS.c_str());

                    // 如果数据库没有保存v,s或者保存的v,s不符合要求则重新计算v,s
                    if (dbV.size() != s_BYTE_SIZE * 2 || dbS.size() != s_BYTE_SIZE * 2) 
                    { _SetVSFields(rI); }
                    else
                    {
                        s.SetHexStr(dbS.c_str());
                        v.SetHexStr(dbV.c_str());
                    }

                    // 计算B
                    b.SetRand(19 * 8);
                    __YXS_BIGNUMBER_ gmod = g.ModExp(b, N);
                    B = ( ( v * 3 ) + gmod ) % N;
                    
                    assert(gmod.GetNumBytes() <= 32);

                    __YXS_BIGNUMBER_ unk3;
                    unk3.SetRand(16 * 8);

                    pkt << (::std::uint8_t)WOW_SUCCESS;
                    pkt.Append(B.AsByteArray(32), 32);
                    pkt << (::std::uint8_t)1;
                    pkt.Append(g.AsByteArray(), 1);
                    pkt << (::std::uint8_t)32;
                    pkt.Append(N.AsByteArray(32), 32);
                    pkt.Append(s.AsByteArray(), s.GetNumBytes());
                    pkt.Append(unk3.AsByteArray(16), 16);

                    ::std::uint8_t securityFlags = 0;
                    pkt << (::std::uint8_t)securityFlags;

                    if (securityFlags & 0x01) 
                    {
                        pkt << ::std::uint32_t(0);
                        pkt << ::std::uint64_t(0) << ::std::uint64_t(0);
                    }

                    if (securityFlags & 0x02)
                    {
                        pkt << ::std::uint8_t(0);
                        pkt << ::std::uint8_t(0);
                        pkt << ::std::uint8_t(0);
                        pkt << ::std::uint8_t(0);
                        pkt << ::std::uint64_t(0);
                    }

                    if (securityFlags & 0x04)
                    {
                        pkt << ::std::uint8_t(1);
                    }

                    // 解析帐号等级和地域
                    ::std::uint8_t secLevel = (*result)[4]->GetUInt8();
                    accountSecLv_ = secLevel <= SEC_ADMINISTRATOR ? eAccountTypes(secLevel) : SEC_ADMINISTRATOR;

                    localizationName_.resize(4);
                    for (int i = 0; i < 4; ++i)
                    { localizationName_[i] = ch->country[4 - i - 1]; }

                    MWOW_INFO_F("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", 
                        loginName_.c_str(), ch->country[3], ch->country[2], 
                        ch->country[1], ch->country[0], GetLocaleByName(localizationName_));
                }
            }
        }
        else                                                // no account
        {
            pkt << (::std::uint8_t) WOW_FAIL_UNKNOWN_ACCOUNT;
        }
    }

    Send((::std::uint8_t*)pkt.Contents(), pkt.Size());
    return true;
}

bool RealmdSocket::_HandleLogonProof(void)
{
    MWOW_DEBUG_F("Entering _HandleLogonProof");
    
    // 读取数据包
    sAuthLogonProof_C lp;
    if (!Recv((::std::uint8_t*)&lp, sizeof(sAuthLogonProof_C)))
    { return false; }

    // 目前只支持6141版本客户端
    if (build_ != 6141)
    {
        // 客户端版本不匹配
        __YXS_BYTEBUFFER_ pkt;
        pkt << (::std::uint8_t) CMD_AUTH_LOGON_CHALLENGE;
        pkt << (::std::uint8_t) 0x00;
        pkt << (::std::uint8_t) WOW_FAIL_VERSION_INVALID;
        MWOW_DEBUG_F("[AuthChallenge] %u is not a valid client version!", build_);
        Send((::std::uint8_t*)pkt.Contents(), pkt.Size());
        return true;
    }


    // SRP6 验证
    __YXS_BIGNUMBER_ A;

    A.SetBinary(lp.A, 32);

    // SRP 安全检查: A必须不等于0
    if (A.IsZero())
    { return false; }

    __YXS_SHA1_ sha;
    sha.UpdateBigNumbers(&A, &B, NULL);
    sha.Finalize();
    __YXS_BIGNUMBER_ u;
    u.SetBinary(sha.GetDigest(), 20);
    __YXS_BIGNUMBER_ S = (A * (v.ModExp(u, N))).ModExp(b, N);

    ::std::uint8_t t[32];
    ::std::uint8_t t1[16];
    ::std::uint8_t vK[40];
    memcpy(t, S.AsByteArray(32), 32);
    for (int i = 0; i < 16; ++i)
    {
        t1[i] = t[i * 2];
    }
    sha.Initialize();
    sha.UpdateData(t1, 16);
    sha.Finalize();
    for (int i = 0; i < 20; ++i)
    {
        vK[i * 2] = sha.GetDigest()[i];
    }
    for (int i = 0; i < 16; ++i)
    {
        t1[i] = t[i * 2 + 1];
    }
    sha.Initialize();
    sha.UpdateData(t1, 16);
    sha.Finalize();
    for (int i = 0; i < 20; ++i)
    {
        vK[i * 2 + 1] = sha.GetDigest()[i];
    }
    K.SetBinary(vK, 40);

    ::std::uint8_t hash[20];

    sha.Initialize();
    sha.UpdateBigNumbers(&N, NULL);
    sha.Finalize();
    memcpy(hash, sha.GetDigest(), 20);
    sha.Initialize();
    sha.UpdateBigNumbers(&g, NULL);
    sha.Finalize();
    for (int i = 0; i < 20; ++i)
    {
        hash[i] ^= sha.GetDigest()[i];
    }
    __YXS_BIGNUMBER_ t3;
    t3.SetBinary(hash, 20);

    sha.Initialize();
    sha.UpdateData(loginName_);
    sha.Finalize();
    ::std::uint8_t t4[SHA_DIGEST_LENGTH];
    memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH);

    sha.Initialize();
    sha.UpdateBigNumbers(&t3, NULL);
    sha.UpdateData(t4, SHA_DIGEST_LENGTH);
    sha.UpdateBigNumbers(&s, &A, &B, &K, NULL);
    sha.Finalize();
    __YXS_BIGNUMBER_ M;
    M.SetBinary(sha.GetDigest(), 20);

    if (!memcmp(M.AsByteArray(), lp.M1, 20))
    {
        // 验证成功

        MWOW_INFO_F("User '%s' successfully authenticated", loginName_.c_str());

        // 更新帐号会话KEY，最后登录IP，最后登录时间，登录区域，失败登陆次数
        const char* K_hex = K.AsHexStr();
        gLoginDB.Execute("UPDATE account SET sessionkey = '%s', last_ip = '%s', last_login = NOW(), locale = '%u', failed_logins = 0 WHERE username = '%s'", 
            K_hex, GetPeerAddr().c_str(), GetLocaleByName(localizationName_), safeLoginName_.c_str());
        OPENSSL_free((void*)K_hex);

        // 向客户端发送SRP6验证数据
        sha.Initialize();
        sha.UpdateBigNumbers(&A, &M, &K, NULL);
        sha.Finalize();
        _SendProof(sha);

        // 设置为已通过验证
        authed_ = true;
    }
    else
    {
        // 验证失败
        ::std::uint8_t data[2] = { CMD_AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT};
        Send(data, sizeof(data));
    }

    return true;
}

bool RealmdSocket::_HandleReconnectChallenge(void)
{
    return false;
}

bool RealmdSocket::_HandleReconnectProof(void)
{
    return false;
}

bool RealmdSocket::_HandleRealmList(void)
{
    MWOW_DEBUG_F("Entering _HandleRealmList");
    if (RecvSize() < 5)
        return false;
    RecvSkip(5);

    __YXS_SQLRESULTSET_PTR_ result = gLoginDB.Query(
        "SELECT id, sha_pass_hash FROM account WHERE username='%s'", safeLoginName_.c_str());
    if (!result)
    {
        // 发送错误，关闭连接
        MWOW_ERROR_F("[ERROR] user %s tried to login and we can not find him in the database.", loginName_.c_str());
        Close();
        return false;
    }

    uint32_t id = (*result)[0]->GetUInt32();
    std::string rI = (*result)[1]->GetString();

    // 加载服务器列表
    __YXS_BYTEBUFFER_ rmpkt;
    _LoadRealmlist(rmpkt, id);

    __YXS_BYTEBUFFER_ pkt;
    pkt << (uint8_t) CMD_REALM_LIST;
    pkt << (uint16_t) rmpkt.Size();
    pkt.Append(rmpkt);

    Send((::std::uint8_t*)pkt.Contents(), pkt.Size());
    return true;
}

void RealmdSocket::_SetVSFields(const ::std::string& rI)
{
    // 计算v,s

    s.SetRand(s_BYTE_SIZE * 8);

    __YXS_BIGNUMBER_ I;
    I.SetHexStr(rI.c_str());

    // In case of leading zeros in the rI hash, restore them
    ::std::uint8_t mDigest[SHA_DIGEST_LENGTH];
    memset(mDigest, 0, SHA_DIGEST_LENGTH);
    if (I.GetNumBytes() <= SHA_DIGEST_LENGTH)
    { memcpy(mDigest, I.AsByteArray(), I.GetNumBytes()); }

    std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH);

    __YXS_SHA1_ sha;
    sha.UpdateData(s.AsByteArray(), s.GetNumBytes());
    sha.UpdateData(mDigest, SHA_DIGEST_LENGTH);
    sha.Finalize();
    __YXS_BIGNUMBER_ x;
    x.SetBinary(sha.GetDigest(), sha.GetLength());
    v = g.ModExp(x, N);
    
    // 把v,s保存到数据库
    const char* v_hex, *s_hex;
    v_hex = v.AsHexStr();
    s_hex = s.AsHexStr();
    gLoginDB.Execute("UPDATE account SET v = '%s', s = '%s' WHERE username = '%s'", v_hex, s_hex, safeLoginName_.c_str());
    __YXS_BIGNUMBER_::Free((void*)v_hex);
    __YXS_BIGNUMBER_::Free((void*)s_hex);
}

void RealmdSocket::_SendProof(__YXS_SHA1_& sha)
{
    sAuthLogonProof_S_BUILD_6005 proof;
    memcpy(proof.M2, sha.GetDigest(), 20);
    proof.cmd = CMD_AUTH_LOGON_PROOF;
    proof.error = 0;
    proof.unk2 = 0x00;

    Send((::std::uint8_t*)&proof, sizeof(proof));
}

void RealmdSocket::_LoadRealmlist(__YXS_BYTEBUFFER_& pkt, ::std::uint32_t id)
{
    // 更新服务器列表
    gRealmList.Update();

    RealmList::RealmArray realms = gRealmList.GetRealms();

    pkt << ::std::uint32_t(0);
    pkt << ::std::uint8_t(realms.size());

    // 将数据填充到数据包
    RealmList::RealmArray::iterator iter = realms.begin();
    for (; iter != realms.end(); ++ iter)
    {
        __YXS_SQLRESULTSET_PTR_ result = gLoginDB.Query(
            "SELECT numchars FROM realmcharacters WHERE realmid = %d AND acctid = %d;",
            iter->id, id);

        uint8_t chars = 0;
        if (result)
            chars = (*result)[0]->GetUInt8();

        pkt << uint32_t(iter->icon);            // icon
        pkt << uint8_t(iter->realmflags);       // realm flag
        pkt << iter->name;                      // 服务器名
        pkt << iter->address;                   // 服务器地址
        pkt << iter->populationLevel;           // population
        pkt << chars;                           // 帐号在该服务器的角色数量
        pkt << iter->timezone;                  // 时区
        pkt << uint8_t(0x00);                   // unk
    }

    pkt << uint16_t(0x0002);
}